package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.feign.product.SkuDetailFeignClient;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.item.to.CategoryView;
import com.atguigu.gmall.item.to.SkuDetailTO;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.starter.cache.annotation.GmallCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.atguigu.gmall.item.to.SkuDetailTO.SkuInfoVo;


import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {


    @Autowired
    SkuDetailFeignClient skuDetailFeignClient;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;

//    @Autowired
//    CacheService cacheService;
//    @Autowired
//    BitmapService bitmapService;

    @Autowired
    ThreadPoolExecutor executor;

//    重点！！！！！！！！！
//    缓存过程： 解决缓存击穿，缓存穿透，缓存雪崩
//    高并发场景缓存使用逻辑
//

    @GmallCache(
            key = RedisConst.SKU_INFO_CACHE_KEY+"#{#params[0]}",
            bitmapName = RedisConst.BITMAP_SKUIDS,
            bitmapOffsetExpr = "#{#params[0]}",
            dataTTl = RedisConst.SKU_INFO_TTL
    )
    @Override
    public SkuDetailTO getSkuDetail(Long skuId) {

        log.info("回源查询数据中...");
        return getSkuDetailTOFromSource(skuId);


//        return getSkuDetailWithRedisson(skuId);
    }


/**
 * 从数据源头要到商品详情数据
 *
 * 1、异步优化：加速访问
 *   1）、线程池
 *   2）、异步编排:  CompletableFuture   异步编排 = 编组 + 排列；
 *      K8s：Kubernetes： 容器编排（自动扩缩容、故障转移、自动修复、负载均衡、灰度发布）
 *      Elatic-Job：任务【编排】；  xxl-job
 *
 *
 *      大量异步场景下； 异步任务之前又有着千丝万缕的关系，写起来很麻烦
 *      ABCDEFGH：这些事情有写可以并发，有些需要同步等待
 *
 *      商品详情查询：
 *          商品分类层级查询：
 *          商品销售属性：
 *          商品valueSkuJson查询：
 *      商品图片：
 *      商品价格：
 */


//从数据源头要到商品详情数据
    private SkuDetailTO getSkuDetailTOFromSource(Long skuId) {

        log.info("商品详情：【{}】； 缓存未命中；正在回源", skuId);

        //        返回的内容
        SkuDetailTO skuDetailTO = new SkuDetailTO();

        //封装sku基本信息
        SkuInfoVo infoVo = new SkuInfoVo();

//        1. 找商品的详细
//        使用CompletableFuture
//        supplyAsync
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {

            SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();


//            infoVo.setId(skuInfo.getId());
//            infoVo.setSkuName(skuInfo.getSkuName());
//            infoVo.setSpuId(skuInfo.getSpuId());
//            infoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
//            infoVo.setWeight(skuInfo.getWeight());


            infoVo.setId(skuInfo.getId());
            infoVo.setSkuName(skuInfo.getSkuName());
            infoVo.setSpuId(skuInfo.getSpuId());
            infoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
            infoVo.setWeight(skuInfo.getWeight());

            skuDetailTO.setSkuInfo(infoVo);

            return skuInfo;
        }, executor);


//        基本信息
//        .getData()调用里面的数据
//        SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();
//        SkuDetailTO.SkuInfoVo skuInfoVo = new SkuDetailTO.SkuInfoVo();
//        BeanUtils.copyProperties(skuInfo, skuInfoVo);
//        skuDetailTO.setSkuInfo(skuInfoVo);



//       2 图片

        CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
            List<SkuDetailTO.SkuImageVo> imageVos = skuDetailFeignClient.getImages(skuId)
                    .getData().stream()
                    .map(item -> {
                        SkuDetailTO.SkuImageVo imageVo = new SkuDetailTO.SkuImageVo();
                        imageVo.setImgUrl(item.getImgUrl());
                        return imageVo;
                    }).collect(Collectors.toList());

            //填充图片
            infoVo.setSkuImageList(imageVos);
            skuDetailTO.setSkuInfo(infoVo);

        }, executor);
//        List<SkuImage> data = skuDetailFeignClient.getImages(skuId).getData();
////        data和skuimageVo的内容不一样，用stream.map一一对应
//        List<SkuDetailTO.SkuImageVo> skuImageVoList = data.stream()
//                .map(item -> {
//                    SkuDetailTO.SkuImageVo skuImageVo = new SkuDetailTO.SkuImageVo();
//                    skuImageVo.setImgUrl(item.getImgUrl());
//                    return skuImageVo;
//                }).collect(Collectors.toList());
//        skuInfoVo.setSkuImageList(skuImageVoList);
//        skuDetailTO.setSkuInfo(skuInfoVo);


//          thenAcceptAsync 能接上次的结果，且此次执行完成后没有返回值
//       3  分类路径
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(result -> {
            CategoryView categoryView = skuDetailFeignClient.getCategoryView(result.getCategory3Id()).getData();
            skuDetailTO.setCategoryView(categoryView);
        }, executor);

        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            BigDecimal decimal = skuDetailFeignClient.getPrice(skuId).getData();
            skuDetailTO.setPrice(decimal);
        },executor);


        //5、销售属性
        CompletableFuture<Void> spuSaleAttrFuture = skuInfoFuture.thenAcceptAsync(result -> {
            List<SpuSaleAttr> data = skuDetailFeignClient.getSpuSaleAttr(result.getSpuId(), skuId).getData();
            skuDetailTO.setSpuSaleAttrList(data);
        },executor);


        //6、valuesSkuJson: 告诉前端每一种组合对应的是哪个商品。
        CompletableFuture<Void> valuesSkuJsonFuture = skuInfoFuture.thenAcceptAsync(result -> {
            String json = skuDetailFeignClient.getValueSkuJson(result.getSpuId()).getData();
            skuDetailTO.setValuesSkuJson(json);
        },executor);


        CompletableFuture
                .allOf(imageFuture,categoryFuture,priceFuture,spuSaleAttrFuture,valuesSkuJsonFuture)
                .join();

////       4 查询价格
//        BigDecimal price = skuDetailFeignClient.getPrice(skuId).getData();
//        skuDetailTO.setPrice(price);
//
////        销售属性
//        List<SpuSaleAttr> spuSaleAttrList = skuDetailFeignClient.getSpuSaleAttr(skuInfo.getSpuId(), skuId).getData();
//        skuDetailTO.setSpuSaleAttrList(spuSaleAttrList);
//
//
//
//        skuDetailTO.setValuesSkuJson("");

        return skuDetailTO;
    }



    //    缓存写在@GmallCache中
//    private SkuDetailTO getSkuDetailWithRedisson(Long skuId) {
//        String skuCacheKey = RedisConst.SKU_INFO_CACHE_KEY + skuId;
//
//        //1、查看缓存
//        SkuDetailTO cacheData = cacheService.get(skuCacheKey, SkuDetailTO.class);
//
////        2缓存中存在
//        if (cacheData != null) {
//            return cacheData;
//        }
//
////        3 缓存中不存在
//        log.info("缓存没有命中，回源，1看位图");
////        3解决缓存击穿  --- 设置位图，看位图中是否存在数据
//        boolean contains = bitmapService.contains(RedisConst.BITMAP_SKUIDS, skuId);
////        3位图不存在
//        if (!contains) {
//
//            log.warn("疑似攻击请求，已被拦截，访问商品：[{}]", skuId);
//            throw new GmallException(ResultCodeEnum.SKU_NOT_EXIST);
//        }
//
////        4 缓存穿透  ---- 回源之前加redisson锁
////        4 位图中有，可以进行回源  加锁：【防击穿】  lock:sku:info:49
//        RLock lock = redissonClient.getLock(RedisConst.LOCK_PREFIX + skuCacheKey);
//        boolean b = lock.tryLock();
//        if (b) {
//            try {
//                //5、加锁成功，可以回源
//                log.info("缓存未命中，开始回源: 步骤2: 加锁放击穿完成...");
//
//                SkuDetailTO source = getSkuDetailTOFromSource(skuId);
//                //6、把回源的数据放入缓存； 商品详情保存15天； 所有的时间都以毫秒为单位
////                 6 将数据放入缓存中， 将缓存的过期时间加上随机值，解决缓存雪崩
//                cacheService.save(skuCacheKey, source, RedisConst.SKU_INFO_TTL);
//                return source;
//            } finally {
//                try {
//                    lock.unlock();
//                } catch (Exception e) {
//                    log.warn("回源完成，差点解了别人的锁...");
//                }
//            }
//        }
//        return null;
//    }
//
//    //    发送请求调用product
////    简单缓存，没啥屁用
////    @Override
//    public SkuDetailTO getSkuDetailEasyCache(Long skuId) {
//
//        ReentrantLock lock = new ReentrantLock(); //本地锁
//
////        RLock lock = redissonClient.getLock("lock");
//
////        1.先看缓存
////        设置缓存格式为 sku:info:{skuid}， 这样在redis中是分层存储
//        String json = redisTemplate.opsForValue().get(RedisConst.SKU_INFO_CACHE_KEY + skuId);
//
////        缓存中有
//        if (!StringUtils.isEmpty(json)) {
//            return JSONs.jsonStrToObj(json, SkuDetailTO.class);
//        }
//
////        缓存中没有，回源， 回源操作之前要先加锁
////        redissonClient.getLock();
//
//
//        lock.lock(); //更细粒度，49号商品用49号专用锁。
//
//        //3、缓存中没有；回源
//        SkuDetailTO source = getSkuDetailTOFromSource(skuId);
//        //4、放入缓存
//        redisTemplate.opsForValue().set(RedisConst.SKU_INFO_CACHE_KEY + skuId,JSONs.toJSONStr(source));
//        lock.unlock();
//        return source;
//
//    }

}
